
package com.lili.collection.core.processor;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;

import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *描述: json基于保存完整结构体的去扁平化处理器
 *@date: 2023/10/28  16:01
 *@author: lili
 */
public class JSONFullFormatUnFlatProcessor {

    private final Object root;
    private Character separator = '.';
    private Character leftBracket = '[';
    private Character rightBracket = ']';

    public static String unflatten(Map<String,Object> jsonConfig) {
        return new JSONFullFormatUnFlatProcessor(jsonConfig).unflatten();
    }

    public static String unflatten(List<Map<String,Object>> jsonConfig) {
        return new JSONFullFormatUnFlatProcessor(jsonConfig).unflatten();
    }

    public JSONFullFormatUnFlatProcessor(Object jsonConfig) {
        root = jsonConfig;
    }
    private String arrayIndex() {
        return Pattern.quote(leftBracket.toString()) + "\\s*\\d+\\s*"
                + Pattern.quote(rightBracket.toString());
    }

    private String objectComplexKey() {
        return Pattern.quote(leftBracket.toString()) + "\\s*\".+?\"\\s*"
                + Pattern.quote(rightBracket.toString());
    }

    private String objectKey() {
        return "[^" + Pattern.quote(separator.toString())
                + Pattern.quote(leftBracket.toString())
                + Pattern.quote(rightBracket.toString()) + "]+";
    }

    private Pattern keyPartPattern() {
        return Pattern.compile(arrayIndex() + "|" + objectComplexKey() + "|" + objectKey());
    }

    public String unflatten() {
        if (root instanceof List) {
            return JSON.toJSONString(unflattenArray((List) root), JSONWriter.Feature.WriteMapNullValue);
        }
        if (!(root instanceof Map)) {
            return root.toString();
        }
        Map<String,Object> flattened=(Map<String,Object>)root;
        Object unflattened = flattened.keySet().isEmpty() ? new JSONObject() : null;

        for (String key : flattened.keySet()) {
            Object currentVal = unflattened;
            String objKey = null;
            Integer aryIdx = null;

            Matcher matcher = keyPartPattern().matcher(key);
            while (matcher.find()) {
                String keyPart = matcher.group();

                if (objKey != null ^ aryIdx != null) {
                    if (isJsonArray(keyPart)) {
                        currentVal = findOrCreateJsonArray(currentVal, objKey, aryIdx);
                        objKey = null;
                        aryIdx = extractIndex(keyPart);
                    } else { // JSON object
                        if (flattened.get(key) instanceof JSONArray) {
                            flattened.put(key, unflattenArray((JSONArray) flattened.get(key)));
                        }
                        currentVal = findOrCreateJsonObject(currentVal, objKey, aryIdx);
                        objKey = extractKey(keyPart);
                        aryIdx = null;
                    }
                }

                if (objKey == null && aryIdx == null) {
                    if (isJsonArray(keyPart)) {
                        aryIdx = extractIndex(keyPart);
                        if (currentVal == null){
                            currentVal = new JSONArray();
                        }
                    } else { // JSON object
                        objKey = extractKey(keyPart);
                        if (currentVal == null) {
                            currentVal = new JSONObject();
                        }
                    }
                }

                if (unflattened == null) unflattened = currentVal;
            }

            setUnflattenedValue(flattened, key, currentVal, objKey, aryIdx);
        }

        return JSON.toJSONString(unflattened,JSONWriter.Feature.WriteMapNullValue);
    }

    private JSONArray unflattenArray(List array) {
        JSONArray unflattenArray = new JSONArray();
        for (Object value : array) {
            if (value instanceof JSONArray) {
                unflattenArray.add(unflattenArray((JSONArray) value));
            } else if (value instanceof JSONObject) {
                unflattenArray.add(JSON.parse((new JSONFullFormatUnFlatProcessor(value.toString()).unflatten())));
            } else {
                unflattenArray.add(value);
            }
        }
        return unflattenArray;
    }

    private String extractKey(String keyPart) {
        if (keyPart.matches(objectComplexKey())) {
            keyPart = keyPart.replaceAll(
                    "^" + Pattern.quote(leftBracket.toString()) + "\\s*\"", "");
            keyPart = keyPart.replaceAll(
                    "\"\\s*" + Pattern.quote(rightBracket.toString()) + "$", "");
        }
        return keyPart;
    }

    private Integer extractIndex(String keyPart) {
        return Integer.valueOf(keyPart.replaceAll("[" + Pattern.quote(leftBracket.toString())
                        + Pattern.quote(rightBracket.toString()) + "\\s]", ""));
    }

    private boolean isJsonArray(String keyPart) {
        return keyPart.matches(arrayIndex()) || keyPart.matches("\\d+");
    }

    private Object findOrCreateJsonArray(Object currentVal, String objKey, Integer aryIdx) {
        if (objKey != null) {
            JSONObject jsonObj = (JSONObject) currentVal;
            if (jsonObj.get(objKey) == null) {
                JSONArray ary = new JSONArray();
                jsonObj.put(objKey, ary);
                return ary;
            }
            return jsonObj.get(objKey);
        } else {
            JSONArray jsonAry = new JSONArray();
            if (jsonAry.size() <= aryIdx) {
                JSONArray ary = new JSONArray();
                assureJsonArraySize(jsonAry, aryIdx);
                jsonAry.set(aryIdx, ary);
                return ary;
            }
            return jsonAry.get(aryIdx);
        }
    }

    private Object findOrCreateJsonObject(Object currentVal, String objKey, Integer aryIdx) {
        if (objKey != null) {
            JSONObject jsonObj = (JSONObject) currentVal;
            if (jsonObj.get(objKey) == null) {
                JSONObject obj = new JSONObject();
                jsonObj.put(objKey, obj);
                return obj;
            }
            return jsonObj.get(objKey);
        } else {
            JSONArray jsonAry = (JSONArray)currentVal;
            if (jsonAry.size() <= aryIdx) {
                JSONObject obj = new JSONObject();
                assureJsonArraySize(jsonAry, aryIdx);
                jsonAry.set(aryIdx, obj);
                return obj;
            }
            return jsonAry.get(aryIdx);
        }
    }

    private void setUnflattenedValue(Map<String,Object> flattened, String key, Object currentVal, String objKey, Integer aryIdx) {
        Object val = flattened.get(key);
        if (objKey != null) {
            if (val instanceof JSONArray ) {
                JSONArray jsonArray = new JSONArray();
                for (Object arrayVal : (JSONArray)val) {
                    jsonArray.add(JSON.parse(new JSONFullFormatUnFlatProcessor(arrayVal.toString()).unflatten()));
                }
                ((JSONObject)currentVal).put(objKey, jsonArray);
            } else {
                ((JSONObject)currentVal).put(objKey, val);
            }
        } else { // aryIdx != null
            assureJsonArraySize((JSONArray) currentVal, aryIdx);
            ((JSONArray)currentVal).set(aryIdx, val);
        }
    }

    private void assureJsonArraySize(JSONArray jsonArray, Integer index) {
        while (index >= jsonArray.size()) {
            jsonArray.add(null);
        }
    }

    @Override
    public int hashCode() {
        int result = 27;
        result = 31 * result + root.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof JSONFullFormatUnFlatProcessor)) return false;
        return root.equals(((JSONFullFormatUnFlatProcessor) o).root);
    }
}